const viewTypes = [
  Int8Array,
  Uint8Array,
  Int16Array,
  Uint16Array,
  Int32Array,
  Uint32Array,
  Float32Array
];

export enum ViewType {
  Int8 = 0, Uint8, Int16, Uint16, Int32, Uint32, Float32
}

/**
 * Struct是结构化数组中的元素的定义，通过createStructArrayType创建的结构化数组的get(index: number)
 * 方法返回的就是Struct的一个实例（更准确地说，是Struct的子类的实例，createStructArrayType负责创建这个子类）。
 * 结构化的数组实际是对某个类型数组进行了包装，实际存储数据
 * 的还是ArrayBuffer。而我们并不能根据ArrayBuffer的索引直接获取某种类型的数据，而是要通过它的视图，即类型数组的
 * 索引访问。_pos1, _pos2, _pos4, _pos8即使根据类型数组中每个元素占用的字节数（可能是1，2，4或8），从
 * ArrayBuffer的索引换算成类型数组的索引。比如一个Float32Array，变量名是arr，给定ArrayBuffer的索引16，
 * 对应的类型数组的索引_pos8 = 2，因此，我们可以通过arr[2]得到相应的Float32类型的数据。
 *
 * @class Struct
 */
class Struct {
  _pos1: number;
  _pos2: number;
  _pos4: number;
  _pos8: number;
  _structArray: StructArray;

  // The following properties are defined on the prototype of sub classes.
  size: number;     // 每个数组元素的字节数
  alignment: number;

  /**
   * @param {StructArray} structArray The StructArray the struct is stored in
   * @param {number} index The index of the struct in the StructArray.
   * @private
   */
  constructor(structArray: StructArray, index: number) {
      this._structArray = structArray;
      this._pos1 = index * this.size;  // ArrayBuffer中该元素开始的地方
      this._pos2 = this._pos1 / 2;
      this._pos4 = this._pos1 / 4;
      this._pos8 = this._pos1 / 8;
  }
}

const DEFAULT_CAPACITY = 128;
const RESIZE_MULTIPLIER = 5;

export interface  StructArrayMember {
  name: string;
  type: ViewType;
  components: number;
  offset: number;
}

export interface SerializedStructArray {
  length: number;
  arrayBuffer: ArrayBuffer;
}

export interface StructArrayTypeParameters {
  members: Array<{name: string, type: ViewType, components?: number}>; // 数组元素的成员的定义
  alignment?: number; // 数组元素成员的字节数
}

/**
 * The StructArray class is inherited by the custom StructArrayType classes created with
 * `createStructArrayType(members, options)`.
 *
 * @class StructArray
 */
export class StructArray {
  capacity: number;  // 数组的容量，默认是128
  length: number; // 数组的长度
  isTransferred: boolean; // web workers中的transferrable object
  arrayBuffer: ArrayBuffer; // 类型数组的缓冲区
  int8?: Int8Array; // 以下是缓冲区的视图，可设定不同的数据类型
  uint8: Uint8Array;
  uint8clamped?: Uint8ClampedArray;
  int16?: Int16Array;
  uint16?: Uint16Array;
  int32?: Int32Array;
  uint32?: Uint32Array;
  float32?: Float32Array;
  float64?: Float64Array;

  // The following properties are defined on the prototype.
  members: Array<StructArrayMember>;   // 数组元素的成员
  StructType: typeof Struct;        // 数组元素的类定义
  bytesPerElement: number;      // 每个数组元素的字节数
  _usedTypes: Array<ViewType>;  // 数组元素的成员用到的类型
  emplaceBack: Function;        // 给数组添加元素的方法

  constructor(serialized?: SerializedStructArray) {
    this.isTransferred = false;

    if (serialized !== undefined) {
    // Create from an serialized StructArray
      this.arrayBuffer = serialized.arrayBuffer;
      this.length = serialized.length;
      this.capacity = this.arrayBuffer.byteLength / this.bytesPerElement;
      this._refreshViews();

    // Create a new StructArray
    } else {
      this.capacity = -1;
      this.resize(0);
    }
  }

  /**
   * Serialize the StructArray type. This serializes the *type* not an instance of the type.
   */
  static serialize(): StructArrayTypeParameters {
    return {
      members: this.prototype.members,
      alignment: this.prototype.StructType.prototype.alignment
    };
  }

  /**
   * Serialize this StructArray instance
   */
  serialize(transferables?: Array<ArrayBuffer>): SerializedStructArray {
    // assert(!this.isTransferred);

    this._trim();

    if (transferables) {
      this.isTransferred = true;
      transferables.push(this.arrayBuffer);
    }
    return {
      length: this.length,
      arrayBuffer: this.arrayBuffer
    };
  }

  /**
   * Return the Struct at the given location in the array.
   * @param {number} index The index of the element.
   */
  get(index: number) {
    // assert(!this.isTransferred);
    return new this.StructType(this, index);
  }

  /**
   * Resize the array to discard unused capacity.
   */
  _trim() {
    if (this.length !== this.capacity) {
      this.capacity = this.length;
      this.arrayBuffer = this.arrayBuffer.slice(0, this.length * this.bytesPerElement);
      this._refreshViews();
    }
  }

  /**
   * Resets the the length of the array to 0 without de-allocating capcacity.
   */
  clear() {
      this.length = 0;
  }

  /**
   * Resize the array.
   * If `n` is greater than the current length then additional elements with undefined values are added.
   * If `n` is less than the current length then the array will be reduced to the first `n` elements.
   * @param {number} n The new size of the array.
   */
  resize(n: number) {
    // assert(!this.isTransferred);

    this.length = n;
    if (n > this.capacity) {
      this.capacity = Math.max(n, Math.floor(this.capacity * RESIZE_MULTIPLIER), DEFAULT_CAPACITY);
      this.arrayBuffer = new ArrayBuffer(this.capacity * this.bytesPerElement);

      const oldUint8Array = this.uint8;
      this._refreshViews();
      if (oldUint8Array) this.uint8.set(oldUint8Array);
    }
  }

  /**
   * Create TypedArray views for the current ArrayBuffer.
   */
  _refreshViews() {
    for (const type of this._usedTypes) {
      // $FlowFixMe
      this[getArrayViewName(type)] = new viewTypes[type](this.arrayBuffer);
    }
  }

  /**
   * Output the `StructArray` between indices `startIndex` and `endIndex` as an array of `StructTypes` to enable sorting
   * @param {number} startIndex
   * @param {number} endIndex
   */
  toArray(startIndex: number, endIndex: number) {
    // assert(!this.isTransferred);

    const array = [];

    for (let i = startIndex; i < endIndex; i++) {
      const struct = this.get(i);
      array.push(struct);
    }

    return array;
  }
}

const structArrayTypeCache: {[key: string]: typeof StructArray} = {};

/**
 * `createStructArrayType`用来生成新的`StructArray`类型。
 *
 * `StructArray`提供了对`ArrayBuffer`和`TypedArray`的抽象，就像一个结构化类型的数组。
 * StructArray由数组元素组成，每个元素有一系列的成员，这些成员在创建`StructArrayType`时定义。
 *
 * StructArrays在创建大的数组时具有以下特点:
 * - 作为Transferable对象从web workers传递数据
 * - 复制起来开销较小
 * - 低精度的成员使用更少的内存
 * - 作为WebGL的缓冲区使用
 *
 * 用法：
 * 通过createStructArrayType()方法创建的StructArray，可以调用emplaceBack()方法添加数据，参数的顺序和数量要
 * 与members里定义的保持一致。StructArray的get(i:number)方法取回数组的第i个元素（类型是Struct）。该元素中的成员与members里定义的
 * 一致。通过Struct[member.name]可以取回某个成员的值，如果某个成员的重复次数大于1，可通过Struct[member.name${idx}]取回指定名称的
 * 成员中的第i个，Struct[member.x0]表示名称为x的成员中的第一个, Struct[member.x1]表示名称为x的成员中的第二个。
 * @export
 * @param {StructArrayTypeParameters} options
 * @returns {typeof StructArray}
 */
export function createStructArrayType(options: StructArrayTypeParameters): typeof StructArray {
  const key = JSON.stringify(options);

  if (structArrayTypeCache[key]) {
    return structArrayTypeCache[key];
  }

  const alignment = options.alignment === undefined ? 1 : options.alignment;

  let offset = 0;
  let maxSize = 0;
  const usedTypes = [ViewType.Uint8];

  const members: Array<StructArrayMember> = options.members.map((member) => {
      if (usedTypes.indexOf(member.type) < 0) {
        usedTypes.push(member.type); // 把成员的类型添加到usedTypes数组
      }

      const typeSize = sizeOf(member.type); // 该成员需要的字节数
      /**
       * Math.max(alignment, typeSize)取类型长度和alignment（默认是1）中最大的一个。
       * 也就是说，通过alignment我们可以指定数组元素每个成员的字节数。比如数组元素有两个成员x和y，
       * 其中x是Uint8长度为一个字节，y是Float32长度为四个字节，如果指定alignment等于4，那么x的长度也是四个字节了。
       *
       * 在计算第n个成员时，现有的偏移是offset_n，成员的字节数是size_n，components数量是c_n。
       * 首先会计算memberOffset，并把offset_n的值设为与memberOffset相等。计算方法就是看当前的offset能够容纳多少
       * 个成员n的数据类型，并对offset做适当延伸以便它能容纳的成员n类型的数量刚好是个整数。这样，第n个成员就是从缓冲区
       * 的memberOffset处开始取值，并且offset_n+1 = memberOffset + c_n * size_n
       * 这样做最大的好处是可以把之前的所有成员都当做是和第n个成员相同的类型，便于通过代理的索引号访问成员。
       * 最后，数组元素的长度size=align(offset, Math.max(maxSize, alignment))也是同样的道理，可能会做适当的放大。
       *
       * 比如一个StructArray的数组元素有三个成员组成，类型分别是Uint8, Uint16和Float32，alignment用默认值1，那么
       * Uint8的memberOffset是0（offset是1），Uint16的memberOffset是2（offset是4），Uint16的memberOffset是4（offset是8）
       * 数组元素的长度size等于8。、
       * 以后在访问结构化数组的某个元素a时，如果要取a的第二个成员的值，由于第二个成员的类型是Uint16，offset是2，
       * 通过代理uint16[offset/typeSize]，即uint16[1]就可以取到第二个成员的值。如果要取第三个成员的值，
       * 则通过float32[offset/typeSize],即float32[1]就可以取到第三个成员的值。
       */
      const memberOffset = offset = align(offset, Math.max(alignment, typeSize));
      /* 成员所需要的字节的数量, 比如type是Uint8，components是2，就表示每个成员由两个Uint8整数组成 */
      const components = member.components || 1;
      // maxSize记录成员中占用字节数最多的类型使用的字节数
      maxSize = Math.max(maxSize, typeSize);
      offset += typeSize * components;

      return {
          name: member.name,
          type: member.type,
          components: components,
          offset: memberOffset
      };
  });
  // 数组元素的长度
  const size = align(offset, Math.max(maxSize, alignment));

  class StructType extends Struct {}

  StructType.prototype.alignment = alignment;
  StructType.prototype.size = size;

  for (const member of members) {
      for (let c = 0; c < member.components; c++) {
          let name = member.name;
          if (member.components > 1) {
              name += c;
          }
          if (name in StructType.prototype) {
              throw new Error(`${name} is a reserved name and cannot be used as a member name.`);
          }
          Object.defineProperty(
              StructType.prototype,
              name,
              createAccessors(member, c)
          );
      }
  }

  class StructArrayType extends StructArray {}

  StructArrayType.prototype.members = members;
  StructArrayType.prototype.StructType = StructType;
  StructArrayType.prototype.bytesPerElement = size;
  StructArrayType.prototype.emplaceBack = createEmplaceBack(members, size);
  StructArrayType.prototype._usedTypes = usedTypes;

  structArrayTypeCache[key] = StructArrayType;

  return StructArrayType;
}

function align(offset: number, size: number): number {
  return Math.ceil(offset / size) * size;
}

function sizeOf(type: ViewType): number {
  return viewTypes[type].BYTES_PER_ELEMENT;
}

function getArrayViewName(type: ViewType): string {
  return ViewType[type].toLowerCase();
}

/*
 * > I saw major perf gains by shortening the source of these generated methods (i.e. renaming
 * > elementIndex to i) (likely due to v8 inlining heuristics).
 * - lucaswoj
 */
function createEmplaceBack(members: Array<StructArrayMember>, bytesPerElement: number): Function {
  const usedTypeSizes = [];
  const argNames = [];
  let body =
      "var i = this.length;\n" +
      "this.resize(this.length + 1);\n";

  for (const member of members) {
      const size = sizeOf(member.type);

      // array offsets to the end of current data for each type size
      // var o{SIZE} = i * ROUNDED(bytesPerElement / size);
      if (usedTypeSizes.indexOf(size) < 0) {
          usedTypeSizes.push(size);
          body += `var o${size.toFixed(0)} = i * ${(bytesPerElement / size).toFixed(0)};\n`;
      }

      for (let c = 0; c < member.components; c++) {
          // arguments v0, v1, v2, ... are, in order, the components of
          // member 0, then the components of member 1, etc.
          const argName: string = `v${argNames.length}`;
          // The index for `member` component `c` into the appropriate type array is:
          // this.{TYPE}[o{SIZE} + MEMBER_OFFSET + {c}] = v{X}
          // where MEMBER_OFFSET = ROUND(member.offset / size) is the per-element
          // offset of this member into the array
          const index = `o${size.toFixed(0)} + ${(member.offset / size + c).toFixed(0)}`;
          body += `this.${getArrayViewName(member.type)}[${index}] = ${argName};\n`;
          argNames.push(argName);
      }
  }

  body += "return i;";

  return new Function(argNames.toString(), body);
}

function createMemberComponentString(member: StructArrayMember, component: number) {
  const elementOffset = `this._pos${sizeOf(member.type).toFixed(0)}`;
  const componentOffset = (member.offset / sizeOf(member.type) + component).toFixed(0);
  const index = `${elementOffset} + ${componentOffset}`;
  return `this._structArray.${getArrayViewName(member.type)}[${index}]`;
}

function createAccessors(member: StructArrayMember, c: number) {
  const code = createMemberComponentString(member, c);
  return {
      get: <() => any>new Function(`return ${code};`),
      set: <() => any>new Function("x", `${code} = x;`)
  };
}